<?php
class HttpUtils {

    private $dom;
    private $xpath;
    private $html;
    private $errors = [];

    public function request($url,$data=null){
        $curl=curl_init();
        curl_setopt($curl, CURLOPT_URL, $url);
        if(!empty($data)){
            curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
            curl_setopt($curl,CURLOPT_POST, true);

        }
        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
        $result=curl_exec($curl);
        curl_close($curl);

        return new HttpUtils($result);
    }

    /**
     * 构造函数
     *
     * @param string $html 要解析的HTML内容
     * @param string $encoding 文档编码，默认为UTF-8
     */
    private function __construct($html = '', $encoding = 'UTF-8') {
        $this->dom = new DOMDocument('1.0', $encoding);
        $this->dom->preserveWhiteSpace = false;
        $this->dom->formatOutput = false;
        $this->dom->strictErrorChecking = false;
        $this->dom->recover = true;
        $this->dom->substituteEntities = false;

        if (!empty($html)) {
            $this->loadHtml($html);
        }
    }

    /**
     * 加载HTML内容
     *
     * @param string $html HTML内容
     * @return bool 是否加载成功
     */
    public function loadHtml($html) {
        $this->html = $html;
        $this->errors = [];

        // 禁用libxml错误并启用用户错误处理
        libxml_use_internal_errors(true);

        // 尝试加载HTML
        $loaded = $this->dom->loadHTML($this->html);

        // 收集所有解析错误
        $this->errors = libxml_get_errors();
        libxml_clear_errors();

        // 初始化XPath
        if ($loaded) {
            $this->xpath = new DOMXPath($this->dom);
        }

        return $loaded;
    }

    /**
     * 获取解析过程中产生的错误
     *
     * @return array 错误数组
     */
    private function getErrors() {
        return $this->errors;
    }

    /**
     * 获取解析后的DOMDocument对象
     *
     * @return DOMDocument
     */
    private function getDom() {
        return $this->dom;
    }

    /**
     * 获取解析后的DOMXPath对象
     *
     * @return DOMXPath
     */
    private function getXpath() {
        return $this->xpath;
    }

    /**
     * 获取HTML内容（经过DOM处理后的）
     *
     * @return string
     */
    public function getHtml() {
        return $this->dom->saveHTML();
    }

    /**
     * 获取指定标签的所有元素
     *
     * @param string $tag 标签名
     * @return DOMNodeList
     */
    public function getElementsByTag($tag) {
        return $this->dom->getElementsByTagName($tag);
    }

    /**
     * 通过ID获取元素
     *
     * @param string $id 元素ID
     * @return DOMElement|null
     */
    public function getElementById($id) {
        return $this->dom->getElementById($id);
    }

    /**
     * 通过类名获取元素
     *
     * @param string $className 类名
     * @return DOMNodeList
     */
    public function getElementsByClassName($className) {
        return $this->xpath->query("//*[contains(concat(' ', normalize-space(@class), ' '), ' $className ')]");
    }

    /**
     * 通过CSS选择器获取元素
     *
     * @param string $selector CSS选择器
     * @return DOMNodeList
     */
    public function querySelector($selector) {
        // 简单的CSS选择器转换XPath实现
        $xpath = $this->convertCssSelectorToXpath($selector);
        return $this->xpath->query($xpath);
    }

    /**
     * 获取元素的HTML内容
     *
     * @param DOMElement $element DOM元素
     * @return string
     */
    public function getInnerHtml(DOMElement $element) {
        $innerHtml = '';
        foreach ($element->childNodes as $child) {
            $innerHtml .= $element->ownerDocument->saveHTML($child);
        }
        return $innerHtml;
    }

    /**
     * 设置元素的HTML内容
     *
     * @param DOMElement $element DOM元素
     * @param string $html 要设置的HTML内容
     */
    public function setInnerHtml(DOMElement $element, $html) {
        // 先清空元素
        while ($element->hasChildNodes()) {
            $element->removeChild($element->firstChild);
        }

        // 创建临时文档片段
        $fragment = $this->dom->createDocumentFragment();

        // 尝试追加HTML内容
        @$fragment->appendXML($html);

        // 如果失败，尝试另一种方式
        if (!$fragment->hasChildNodes()) {
            $tempDom = new DOMDocument('1.0', 'UTF-8');
            $tempDom->loadHTML('<htmlfragment>' . $html . '</htmlfragment>');
            $tempFragment = $tempDom->getElementsByTagName('htmlfragment')->item(0);

            foreach ($tempFragment->childNodes as $node) {
                $node = $this->dom->importNode($node, true);
                $fragment->appendChild($node);
            }
        }

        $element->appendChild($fragment);
    }

    /**
     * 获取元素的文本内容（去除HTML标签）
     *
     * @param DOMElement $element DOM元素
     * @return string
     */
    public function getTextContent(DOMElement $element) {
        return $element->textContent;
    }

    /**
     * 获取元素的属性值
     *
     * @param DOMElement $element DOM元素
     * @param string $attribute 属性名
     * @return string|null 属性值，不存在返回null
     */
    public function getAttribute(DOMElement $element, $attribute) {
        return $element->hasAttribute($attribute) ? $element->getAttribute($attribute) : null;
    }

    /**
     * 设置元素的属性值
     *
     * @param DOMElement $element DOM元素
     * @param string $attribute 属性名
     * @param string $value 属性值
     */
    public function setAttribute(DOMElement $element, $attribute, $value) {
        $element->setAttribute($attribute, $value);
    }

    /**
     * 移除元素的属性
     *
     * @param DOMElement $element DOM元素
     * @param string $attribute 属性名
     */
    public function removeAttribute(DOMElement $element, $attribute) {
        $element->removeAttribute($attribute);
    }

    /**
     * 检查元素是否有指定类名
     *
     * @param DOMElement $element DOM元素
     * @param string $className 类名
     * @return bool
     */
    public function hasClass(DOMElement $element, $className) {
        $class = $element->getAttribute('class');
        return in_array($className, preg_split('/\s+/', trim($class)));
    }

    /**
     * 为元素添加类名
     *
     * @param DOMElement $element DOM元素
     * @param string $className 类名
     */
    public function addClass(DOMElement $element, $className) {
        if (!$this->hasClass($element, $className)) {
            $class = $element->getAttribute('class');
            $element->setAttribute('class', trim($class . ' ' . $className));
        }
    }

    /**
     * 从元素中移除类名
     *
     * @param DOMElement $element DOM元素
     * @param string $className 类名
     */
    public function removeClass(DOMElement $element, $className) {
        if ($this->hasClass($element, $className)) {
            $class = $element->getAttribute('class');
            $newClass = preg_replace('/\s*' . preg_quote($className, '/') . '\s*/', ' ', $class);
            $newClass = trim(preg_replace('/\s+/', ' ', $newClass));
            $element->setAttribute('class', $newClass);
        }
    }

    /**
     * 切换元素的类名（存在则移除，不存在则添加）
     *
     * @param DOMElement $element DOM元素
     * @param string $className 类名
     */
    public function toggleClass(DOMElement $element, $className) {
        if ($this->hasClass($element, $className)) {
            $this->removeClass($element, $className);
        } else {
            $this->addClass($element, $className);
        }
    }

    /**
     * 获取元素的父元素
     *
     * @param DOMElement $element DOM元素
     * @return DOMElement|null
     */
    public function getParent(DOMElement $element) {
        return $element->parentNode;
    }

    /**
     * 获取元素的所有子元素
     *
     * @param DOMElement $element DOM元素
     * @return DOMNodeList
     */
    public function getChildren(DOMElement $element) {
        return $element->childNodes;
    }

    /**
     * 获取元素的前一个兄弟元素
     *
     * @param DOMElement $element DOM元素
     * @return DOMElement|null
     */
    public function getPreviousSibling(DOMElement $element) {
        $sibling = $element->previousSibling;
        while ($sibling && $sibling->nodeType !== XML_ELEMENT_NODE) {
            $sibling = $sibling->previousSibling;
        }
        return $sibling;
    }

    /**
     * 获取元素的下一个兄弟元素
     *
     * @param DOMElement $element DOM元素
     * @return DOMElement|null
     */
    public function getNextSibling(DOMElement $element) {
        $sibling = $element->nextSibling;
        while ($sibling && $sibling->nodeType !== XML_ELEMENT_NODE) {
            $sibling = $sibling->nextSibling;
        }
        return $sibling;
    }

    /**
     * 在元素内部追加HTML内容
     *
     * @param DOMElement $element DOM元素
     * @param string $html HTML内容
     */
    public function appendHtml(DOMElement $element, $html) {
        $currentHtml = $this->getInnerHtml($element);
        $this->setInnerHtml($element, $currentHtml . $html);
    }

    /**
     * 在元素内部前置HTML内容
     *
     * @param DOMElement $element DOM元素
     * @param string $html HTML内容
     */
    public function prependHtml(DOMElement $element, $html) {
        $currentHtml = $this->getInnerHtml($element);
        $this->setInnerHtml($element, $html . $currentHtml);
    }

    /**
     * 在元素之前插入HTML内容
     *
     * @param DOMElement $element DOM元素
     * @param string $html HTML内容
     */
    public function insertBefore(DOMElement $element, $html) {
        $fragment = $this->dom->createDocumentFragment();
        @$fragment->appendXML($html);
        $element->parentNode->insertBefore($fragment, $element);
    }

    /**
     * 在元素之后插入HTML内容
     *
     * @param DOMElement $element DOM元素
     * @param string $html HTML内容
     */
    public function insertAfter(DOMElement $element, $html) {
        $fragment = $this->dom->createDocumentFragment();
        @$fragment->appendXML($html);

        if ($element->nextSibling) {
            $element->parentNode->insertBefore($fragment, $element->nextSibling);
        } else {
            $element->parentNode->appendChild($fragment);
        }
    }

    /**
     * 移除元素
     *
     * @param DOMElement $element DOM元素
     */
    public function removeElement(DOMElement $element) {
        $element->parentNode->removeChild($element);
    }

    /**
     * 替换元素
     *
     * @param DOMElement $element 要替换的DOM元素
     * @param string $html 替换的HTML内容
     */
    public function replaceElement(DOMElement $element, $html) {
        $fragment = $this->dom->createDocumentFragment();
        @$fragment->appendXML($html);
        $element->parentNode->replaceChild($fragment, $element);
    }

    /**
     * 获取文档中的所有链接
     *
     * @return array 链接数组
     */
    public function getAllLinks() {
        $links = [];
        $anchorTags = $this->getElementsByTag('a');

        foreach ($anchorTags as $tag) {
            $href = $tag->getAttribute('href');
            if (!empty($href)) {
                $links[] = [
                    'href' => $href,
                    'text' => $this->getTextContent($tag),
                    'title' => $tag->getAttribute('title'),
                    'target' => $tag->getAttribute('target')
                ];
            }
        }

        return $links;
    }

    /**
     * 获取文档中的所有图片
     *
     * @return array 图片数组
     */
    public function getAllImages() {
        $images = [];
        $imgTags = $this->getElementsByTag('img');

        foreach ($imgTags as $tag) {
            $src = $tag->getAttribute('src');
            if (!empty($src)) {
                $images[] = [
                    'src' => $src,
                    'alt' => $tag->getAttribute('alt'),
                    'title' => $tag->getAttribute('title'),
                    'width' => $tag->getAttribute('width'),
                    'height' => $tag->getAttribute('height')
                ];
            }
        }

        return $images;
    }

    /**
     * 获取文档的标题
     *
     * @return string|null 标题内容，没有返回null
     */
    public function getTitle() {
        $titleTags = $this->getElementsByTag('title');
        return $titleTags->length > 0 ? $this->getTextContent($titleTags->item(0)) : null;
    }

    /**
     * 获取文档的meta标签内容
     *
     * @param string $name meta的name属性值
     * @return string|null meta的content属性值，没有返回null
     */
    public function getMetaContent($name) {
        $metas = $this->xpath->query("//meta[@name='$name']");
        if ($metas->length > 0) {
            return $metas->item(0)->getAttribute('content');
        }
        return null;
    }

    /**
     * 获取文档的所有meta标签
     *
     * @return array meta标签数组
     */
    public function getAllMetaTags() {
        $metas = [];
        $metaTags = $this->getElementsByTag('meta');

        foreach ($metaTags as $tag) {
            $name = $tag->getAttribute('name');
            if (empty($name)) {
                $name = $tag->getAttribute('property'); // 支持Open Graph的property属性
            }

            if (!empty($name)) {
                $metas[$name] = $tag->getAttribute('content');
            }
        }

        return $metas;
    }

    /**
     * 简单的CSS选择器转XPath
     *
     * @param string $selector CSS选择器
     * @return string XPath表达式
     */
    private function convertCssSelectorToXpath($selector) {
        // 简单实现，支持基本选择器
        $selector = trim($selector);

        // ID选择器
        if (strpos($selector, '#') === 0) {
            $id = substr($selector, 1);
            return "//*[@id='$id']";
        }

        // 类选择器
        if (strpos($selector, '.') === 0) {
            $class = substr($selector, 1);
            return "//*[contains(concat(' ', normalize-space(@class), ' $class ')]";
        }

        // 属性选择器
        if (preg_match('/^\[([^\]]+)\]$/', $selector, $matches)) {
            $attr = $matches[1];
            return "//*[@$attr]";
        }

        // 默认按标签名处理
        return "//$selector";
    }

    /**
     * 清理HTML，移除不需要的标签和属性
     *
     * @param string $html HTML内容
     * @param array $allowedTags 允许的标签，如['p', 'a', 'img']
     * @param array $allowedAttributes 允许的属性，如['href', 'src', 'alt']
     * @return string 清理后的HTML
     */
    public static function sanitizeHtml($html, $allowedTags = [], $allowedAttributes = []) {
        if (empty($allowedTags)) {
            // 默认允许的标签
            $allowedTags = ['p', 'a', 'span', 'br', 'img', 'strong', 'em', 'ul', 'ol', 'li', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
        }

        if (empty($allowedAttributes)) {
            // 默认允许的属性
            $allowedAttributes = ['href', 'src', 'alt', 'title', 'class', 'target'];
        }

        $dom = new DOMDocument('1.0', 'UTF-8');
        @$dom->loadHTML('<html><body>' . $html . '</body></html>', LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);

        $xpath = new DOMXPath($dom);
        $nodes = $xpath->query('//*');

        foreach ($nodes as $node) {
            // 移除不在白名单中的标签
            if (!in_array(strtolower($node->nodeName), $allowedTags)) {
                $node->parentNode->removeChild($node);
                continue;
            }

            // 处理属性
            if ($node->hasAttributes()) {
                $attributes = $node->attributes;
                $attrsToRemove = [];

                foreach ($attributes as $attr) {
                    if (!in_array(strtolower($attr->name), $allowedAttributes)) {
                        $attrsToRemove[] = $attr->name;
                    }
                }

                foreach ($attrsToRemove as $attrName) {
                    $node->removeAttribute($attrName);
                }
            }
        }

        // 获取body内容
        $body = $dom->getElementsByTagName('body')->item(0);
        $cleanHtml = '';

        foreach ($body->childNodes as $node) {
            $cleanHtml .= $dom->saveHTML($node);
        }

        return $cleanHtml;
    }

    /**
     * 提取纯文本（去除所有HTML标签）
     *
     * @param string $html HTML内容
     * @return string 纯文本
     */
    public static function extractText($html) {
        // 先替换一些特殊字符
        $html = str_replace([' ', "\r", "\n"], [' ', '', ' '], $html);

        // 移除脚本和样式
        $html = preg_replace('@<(script|style)[^>]*?>.*?</\\1>@si', '', $html);

        // 移除HTML标签
        $text = strip_tags($html);

        // 合并多个空格
        $text = preg_replace('/\s+/', ' ', $text);

        return trim($text);
    }

}